local m = require("ufe.ufe_module")
local shared = require("ufe.util.shared")
local cjson = require("cjson.safe")
local table = require("table")

local function print_msg(msg)
    ngx.header["Content-Type"] = 'application/json;charset=utf-8'
    if msg then
        msg = string.gsub(msg, '"', '\\"')
        ngx.print(string.format('{"success":false,"errmsg":"%s"}', msg))
    else
        ngx.print('{"success":true}')
    end
end

local function print_info(apps,switch)
    ngx.header["Content-Type"] = 'application/json;charset=utf-8'
    local result = {success=true}
    result.ufe_apps = apps
    result.ufe_switch = switch

    local res, err = cjson.encode(result)
    if not res then
        err = err or "Unknown error"
        return print_msg("internal Errror:"..err)
    end
    ngx.print(res)
end

local function parse_ip(ip)
    local d = ip % 0x100
    local c = (ip - d)/0x100 % 0x100
    local b = (ip - d - c * 0x100)/0x10000 % 0x100
    local a = (ip - d - c * 0x100 - b * 0x10000)/0x1000000 % 0x100
    return string.format("%d.%d.%d.%d",a,b,c,d)
end

local function get_app(app, switch)
    local app_str = shared.get_app_conf(app)
    if not app_str then
        return {name = app, status = "err", msg="Not found,try it later"}
    else
        local acf, err = cjson.decode(app_str)
        if not acf then
            err = err or "Unkown error"
                return {name = app, status = "err", msg = "internal error:"..err}
        end
        if switch[app] == true then
            acf.status = "on"
            acf.ufe_switch = "on"
        else
            acf.status = "off"
            acf.ufe_switch = "off"
        end

        for _,pg in ipairs(acf.policy_groups) do
            for _,p in ipairs(pg.policies) do
                if p.action and p.action.ips then
                    local ips = {}
                    for i,ip in ipairs(p.action.ips) do
                        if ip.s and ip.e then
                            local s = parse_ip(ip.s)
                            local e = parse_ip(ip.e)
                            ips[i] = string.format("%s-%s",s,e)
                        end
                    end
                    p.action.ips = ips
                end
            end
        end

        return acf
    end
end
local function do_look(app)
    local apps_str = shared.get_ufe_apps()
    if not apps_str then
        return print_msg("cannot get ufe_apps from remote, please try later")
    end

    local apps, err = cjson.decode(apps_str)
    if not apps then
        err = err or "Unknown error"
        return print_msg("Internal Error:"..err)
    end

    --if #apps.ufe_apps == 0 then
    --    return print_msg("emtpy ufe apps")
    --end

    local result = {}
    local switch = shared.get_switch()

    if not app then
        for _, a in ipairs(apps.ufe_apps) do
            result[a] = get_app(a, switch)
        end
    else
        result[app] = get_app(app, switch)
    end

    local s = "off"
    if switch.global == true then
        s = "on"
    end

    return print_info(result, s)
end

-- no matter success or fail ,this action will clear all loaded conf
local function do_reload(app)
    if not shared.lock_ufe_apps() then
        return print_msg("Someone is updating ufe, please try it later, if you have tried for a long time, there may be something wrong with the server, please contact PE to solve the problem")
    end
    if not app then --need to reload all
        shared.clear_loaded_conf() --first clear all loaded conf
        local ok, err = m.get_apps_from_remote()
        if not ok then
            err = err or "unknow error"
            ngx.log(ngx.ERR, "reload apps from remote failed. Error:", err)
            shared.unlock_ufe_apps()
            return print_msg(err)
        end
    else
        if not shared.lock_app(app) then
            shared.unlock_ufe_apps()
            return print_msg("Someone is updating this app, please try it later, if you have tried for a long time, there may be something wrong with the server, please contact PE to solve the problem")
        end

        local apps_str = shared.get_ufe_apps()
        if not apps_str then
            shared.unlock_app(app)
            shared.unlock_ufe_apps()
            return print_msg("Not found ufe apps.")
        end

        local apps, err = cjson.decode(apps_str)
        if not apps or not apps.ufe_apps or not apps.update_time then
            shared.clear_app_conf(app)
            shared.unlock_app(app)
            shared.unlock_ufe_apps()
            -- there must be somethins wrong
            local msg = string.format("the apps in shared dict is wrong, unexpected! Err is %s. raw is %s",err,apps_str)
            return print_msg(msg)
        end

        local exist = false
        for _,a in ipairs(apps.ufe_apps) do
            if a == app then
                exist = true
                break
            end
        end

        if not exist then
            shared.unlock_app(app)
            shared.unlock_ufe_apps()
            return print_msg(string.format("the %s is not in ufe apps",app))
        end
        shared.clear_app_conf(app)
        apps.update_time = ngx.time()
        apps_str = cjson.encode(apps)
        local ok, msg = shared.set_ufe_apps(apps_str)
        if not ok then
            msg = string.format("set ufe apps failed:%s", msg or "unknow err")
        end

        shared.unlock_app(app)
    end

    -- succcess
    shared.unlock_ufe_apps()
    return print_msg()
end

local function do_debug(app)
    ngx.header["Content-Type"] = 'application/json;charset=utf-8'
    result = {success=true}

    local apps = shared.get_ufe_apps()
    local switch = shared.get_switch_raw()
    result.updating = m.updating_conf
    result.apps = apps
    result.switch = switch
    if not app then
        result.shared_conf = {}
        result.ufe_lock = {}
        local keys = shared_conf:get_keys()
        for _, key in ipairs(keys) do
            result.shared_conf[key] = shared_conf:get(key)
        end
        local keys = ufe_lock:get_keys()
        for _, key in ipairs(keys) do
            result.ufe_lock[key] = ufe_lock:get(key)
        end
    else
        result[app] = shared.get_app_conf(app)
        result["error"] = shared.get_app_err(app)
    end

    local res, err = cjson.encode(result)
    if not res then
        err = err or "Unknown error"
        return print_msg("internal Errror:"..err)
    end
    ngx.print(res)
end

local function do_switch(status,app)
    if not status then
        return print_msg("Not found status arg")
    end
    if status ~= 'on' and status ~= 'off' then
        return print_msg("status must be on or off")
    end
    if status == 'on' then
        status = true
    else
        status = false
    end
    local success, err = shared.set_ufe_switch(status,app)
    print_msg(err)

end

local function do_add(app)
    ngx.log(ngx.INFO,string.format("do add app:%s",app))
    if not app then
        return print_msg("Not found app arg")
    end

    if not shared.lock_ufe_apps() then
        return print_msg("Someone is updating ufe, please try it later, if you have tried for a long time, there may be something wrong with the server, please contact PE to solve the problem")
    end

    if not shared.lock_app(app) then
        shared.unlock_ufe_apps()
        return print_msg("Someone is updating this app, please try it later, if you have tried for a long time, there may be something wrong with the server, please contact PE to solve the problem")
    end

    --add to ufe apps
    local apps_str = shared.get_ufe_apps()
    local apps = {ufe_apps={}}
    if apps_str then
        apps, err = cjson.decode(apps_str)
        if not apps or not apps.ufe_apps or not apps.update_time then
            shared.clear_app_conf(app)
            shared.unlock_app(app)
            shared.unlock_ufe_apps()

            -- there must be somethins wrong
            local msg = string.format("the apps in shared dict is wrong, unexpected!err is %s. raw is %s",err,apps_str)
            return print_msg(msg)
        end
    end

    local exist = false
    for _, a in ipairs(apps.ufe_apps) do
        if a == app then
            exist = true
        end
    end
    if exist then
        shared.unlock_app(app)
        shared.unlock_ufe_apps()
        return print_msg(string.format("the %s is already in ufe_apps, try reload it",app))
    end
    table.insert(apps.ufe_apps, app)
    apps.update_time = ngx.time()

    apps_str = cjson.encode(apps)
    local ok, msg = shared.set_ufe_apps(apps_str)
    if not ok then
        msg = string.format("set ufe apps failed:%s", msg or "unknow err")
    end

    shared.unlock_app(app)
    shared.unlock_ufe_apps()
    return print_msg(msg)
end

local function do_clear(app)
    ngx.log(ngx.INFO,string.format("do clear app:%s",app))
    if not shared.lock_ufe_apps() then
        return print_msg("Someone is updating ufe,please try it later, if you have tried for a long time, there may be something wrong with the server, please contact PE to solve the problem")
    end

    if not app then
        return print_msg("Not found app arg")
    elseif app == 'all' then
        --clear all conf in shared memory
        ngx.log(ngx.NOTICE, "clear all apps")
        shared.clear_loaded_conf()
    else
        -- clear this app unconditional
        shared.clear_app_conf(app)
        local apps_str = shared.get_ufe_apps()
        if not apps_str then
            shared.unlock_ufe_apps()
            return print_msg(sgring.format("Not found %s conf",app))
        end
        local apps, err = cjson.decode(apps_str)
        if not apps or not apps.ufe_apps or not apps.update_time then
            shared.unlock_ufe_apps()

            -- there must be somethins wrong
            local msg = string.format("the apps in shared dict is wrong, unexpected!err is %s. raw is %s",err,apps_str)
            return print_msg(msg)
        end
        for i, a in ipairs(apps.ufe_apps) do
            if a == app then
                --only remove one, so remove does work
                table.remove(apps.ufe_apps, i)
                break
            end
        end

        --TODO: clear switch

        --need update apps's update_time
        apps.update_time = ngx.time()
        apps_str = cjson.encode(apps)
        shared.set_ufe_apps(apps_str)
    end
    shared.unlock_ufe_apps()
    return print_msg()
end

-- in case of dead lock
local function do_unlock(app)
    if not app then
        shared.unlock_ufe_apps()
    else
        shared.unlock_app(app)
    end
    return print_msg()
end

-- flush all conf in shared memory
local function do_flush()
    m.updating_conf = false
    shared_conf:flush_all()
    ufe_lock:flush_all()
    return print_msg()
end

local action = ngx.var.arg_action
local app = ngx.var.arg_app
local method = ngx.var.request_method
local status = ngx.var.arg_status

if method ~= "GET" then
    return  print_msg("Not Support Method")
end

if not action then
    return print_msg("Not Found Action arg")
end

if action == "reload" then
    return do_reload(app)
elseif action == "add" then
    return do_add(app)
elseif action == "clear" then
    return do_clear(app)
elseif action == "look" then
    return do_look(app)
elseif action == "switch" then
    return do_switch(status,app)
elseif action == "debug" then
    return do_debug(app)
elseif action == "unlock" then
    return do_unlock(app)
elseif action == "flush" then
    return do_flush(app)
else
    return print_msg("Not Support Action")
end

